package com.brian.mongo.service;

import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @program: architect
 * @author: Brian Huang
 * @create: 2019-07-29 20
 **/
@Component
public class RedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static  String redislockKey = "mongo-demo-lock";

    /**
     *  加锁
     * @param acquireTimeout 获取锁之前的超时时间 单位秒
     * @param timeOut        获取锁之后的超时时间 单位秒
     * @return
     */
    public String getRedisLock(Long acquireTimeout, Long timeOut) {

        try {
            // 1.定义 redis 对应key 的value值( uuid) 作用 释放锁 随机生成value
            String identifierValue = UUID.randomUUID().toString().replace("-","");

            // 2.使用循环机制 如果没有获取到锁，要在规定acquireTimeout时间 保证重复进行尝试获取锁（乐观锁）
            Long endTime = System.currentTimeMillis() + acquireTimeout*1000;
            while (System.currentTimeMillis() < endTime) {
                // 获取锁
                // 3.使用setnx命令插入对应的redislockKey ，如果返回为true 成功获取锁
                if (this.setNx(redislockKey, identifierValue,timeOut)) {
                    return identifierValue;
                }

                // 为什么获取锁之后，还要设置锁的超时时间 目的是为了防止死锁
                // zookeeper实现分布式锁通过什么方式 防止死锁 设置session 有效期
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 释放锁
     * @param identifierValue
     */
    public void unRedisLock(String identifierValue) {

        try {
            // 如果该锁的id 等于identifierValue 是同一把锁情况才可以删除
            String s = stringRedisTemplate.opsForValue().get(redislockKey);
            if (s.equals(identifierValue)) {
                System.out.println("释放锁..." + Thread.currentThread().getName() + ",identifierValue:" + identifierValue);
                stringRedisTemplate.delete(redislockKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 释放锁有两种 key自动有有效期
        // 整个程序执行完毕情况下，删除对应key

    }

    private boolean setNx(String key, String value, long expiredTime) {
        Boolean resultBoolean = null;
        try {
            resultBoolean = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                String redisResult = "";
                @SuppressWarnings("unchecked")
                RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) stringRedisTemplate.getKeySerializer();
                //lettuce连接包下序列化键值，否知无法用默认的ByteArrayCodec解析
                byte[] keyByte = stringRedisSerializer.serialize(key);
                byte[] valueByte = stringRedisSerializer.serialize(value);
                // lettuce连接包下 redis 单机模式setnx
                if (nativeConnection instanceof RedisAsyncCommands) {
                    RedisAsyncCommands commands = (RedisAsyncCommands)nativeConnection;
                    //同步方法执行、setnx禁止异步
                    redisResult = commands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, valueByte, SetArgs.Builder.nx().ex(expiredTime));
                }
                // lettuce连接包下 redis 集群模式setnx
                if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                    redisResult = clusterAsyncCommands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, keyByte, SetArgs.Builder.nx().ex(expiredTime));
                }
                //返回加锁结果
                return "OK".equalsIgnoreCase(redisResult);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultBoolean != null && resultBoolean;
    }

}
